#include "LGSolver.h"

namespace Solver
{

LGSolver::LGSolver(): BaseSolver(SolverType_E::LG_E, "LGSolver"), needDiffSearch_(false), upIter_(0), maxWaitNum_(10), upY_(0), upR_(0)
{

}

LGSolver::~LGSolver()
{

}

void LGSolver::openDiffSearch(bool open)
{
    needDiffSearch_ = open;
}

bool LGSolver::init()
{
    //1.
    vecAvg_.resize(vecMax_.size());

    //2.
    vecWaitVar_.clear();

    //3.
    upIter_     = 0;
    upY_        = std::numeric_limits<value_t>::max();
    maxWaitNum_ = vecVariable_.size() * 8;
    return true;
}

void LGSolver::solveByIter()
{
    //1.
    calcAvg(vecAvg_);

    //2.
    sortBestForVariable();

    //3.
    waitVarIndex_ = vecWaitVar_.size() > vecVariable_.size() ? vecVariable_.size() : vecWaitVar_.size();
    std::shuffle(vecWaitVar_.begin(), vecWaitVar_.end(), std::mt19937{std::random_device{}()});

    //4.
    if (searchBest())
    {
        return;
    }

    //5.
    updateParam();

    //6.
    if (searchAgain())
    {
        return;
    }

    //7. 将当前解放入候选解
    for (auto& var : vecVariable_)
    {
        if (var != bestVar_)
        {
            pushVar(var->current->x);
        }
    }
}

void LGSolver::pushVar(const VectorXd_t &x)
{
    if (waitVarIndex_ < vecWaitVar_.size())
    {
        std::copy(x.begin(), x.end(), vecWaitVar_[waitVarIndex_]->x.begin());
        ++waitVarIndex_;
    }
    else if(vecWaitVar_.size())
    {
        auto ptr = std::make_shared<DataValue_t>(x.size());
        ptr->x   = x;
        vecWaitVar_.push_back(ptr);
    }
}

VectorXd_t& LGSolver::getRandX(bool needWaitVar)
{
    const std::size_t num = needWaitVar ? vecWaitVar_.size() + vecVariable_.size() : vecVariable_.size();
    std::size_t index = getRandInt(0, num);
    if (index < vecVariable_.size())
    {
        return vecVariable_[index]->current->x;
    }
    else
    {
        return vecWaitVar_[index - vecVariable_.size()]->x;
    }
}

void LGSolver::updateParam()
{
    constexpr value_t ConstR = 0.36;
    if (upY_ - bestVar_->best->y > (std::fabs(upY_) + ConstEps1E12) * 0.01 )
    {
        ++upIter_;
        if (std::fabs(upY_ - bestVar_->best->y) < (std::fabs(upY_) + ConstEps1E12) * 0.001)
        {
            upR_ = (668 - upIter_) * ConstR * getRandUniform01() / 666;
        }
        else
        {
            upR_ = (668 - upIter_) * ConstR / 666;
        }

        if (upIter_ > 666)
        {
            upIter_ = 0;
        }
    }
    else
    {
        upR_    = ConstR;
        upIter_ = 0;
        upY_    = bestVar_->best->y;
    }
}

bool LGSolver::searchBest()
{
    const std::size_t num = getRandUniform01() < 0.86 ? 1 : (vecVariable_.size() < 3 ? vecVariable_.size() : 3);
    for (std::size_t i = 0; i != num; ++i)
    {
        if (searchLocal(vecVariable_[i]))
        {
            return true;
        }

        if (getRandUniform01() > 0.8)
        {
            if (searchByDiff(vecVariable_[i]))
            {
                return true;
            }
        }
    }

    return false;
}

bool LGSolver::searchAgain()
{
    value_t r;
    uint8_t k = 0;
    for (auto& var : vecVariable_)
    {
        //1.
        if (var == bestVar_)
        {
            continue;
        }

        //2.
        r = getRandUniform01();
        if (r < 0.36)
        {
            k = 16;
        }
        else if (r < 0.5)
        {
            k = 6;
        }
        else
        {
            k = 1;
        }

        //3.
        for (uint8_t i = 0; i != k; ++i)
        {
            if (searchVar(var, k == 1))
            {
                return true;
            }
        }

        //2.4
        if (needDiffSearch_)
        {
            if (getRandUniform01() > 0.96)
            {
                if(searchByDiff(var))
                {
                    return true;
                }
            }
        }
    }

    return false;
}

bool LGSolver::searchVar(std::shared_ptr<Variable_t>& var, bool forceUpdate)
{
    //1.
    value_t r = getRandUniform01();
    auto& x  = var->opt->x;
    auto& x0 = var->current->x;
    auto& x1 = getRandX(getRandUniform01() < 0.5);
    auto& x2 = getRandX(getRandUniform01() < 0.5);
    auto& x3 = getRandX(getRandUniform01() < 0.5);
    auto& x4 = getRandX(getRandUniform01() < 0.5);
    auto& x5 = getRandX(getRandUniform01() < 0.5);
    auto& xg = bestVar_->best->x;

    //2.
    if (r < upR_)
    {
        r /= upR_;
        if (r < 0.38)
        {
            add3Same(x, getRandUniform01(), x0, x0, xg);
        }
        else if (r < 0.68)
        {
            add5Rand(x, 1, 1, x0, x0, xg, x1, x0);
        }
        else if (r < 0.9)
        {
            add3Same(x, getRandUniform01(), x0, x0, vecAvg_);
        }
        else
        {
            add3Same(x, getRandUniform01(), x0, x0, x1);
        }
    }
    else
    {
        r = (r - upR_) / (1 - upR_);
        if (r < 0.2)
        {
            add5Same(x, getRandUniform01() * 0.6, getRandUniform01() * 0.4, x0, x0, xg, vecAvg_, xg);
        }
        else if (r < 0.4)
        {
            add5Same(x, getRandUniform01() * 0.86, getRandUniform01() * 0.14, x0, x0, xg, x0, x1);
        }
        else if (r < 0.6)
        {
            add7Same(x, getRandUniform01() * 0.36, getRandUniform01() * 0.36, getRandUniform01() * 0.34, x0, x1, xg, x2, vecAvg_, x3, x4);
        }
        else if (r < 0.8)
        {
            add5Rand(x, 1, 1, x0, x0, xg, x1, x0);
        }
        else
        {
            const std::size_t n = x.size();
            for (std::size_t i = 0; i != n; ++i)
            {
                r = getRandUniform01();
                if (r < 0.2)
                {
                    x[i] = x0[i];
                }
                else if (r < 0.3)
                {
                    x[i] = xg[i];
                }
                else if (r < 0.4)
                {
                    x[i] = vecAvg_[i];
                }
                else if (r < 0.5)
                {
                    x[i] = x1[i];
                }
                else if (r < 0.6)
                {
                    x[i] = x2[i];
                }
                else if (r < 0.7)
                {
                    x[i] = x3[i];
                }
                else if (r < 0.8)
                {
                    x[i] = x4[i];
                }
                else if (r < 0.9)
                {
                    x[i] = x5[i];
                }
                else
                {
                    x[i] = ((vecMax_[i] + vecMin_[i])) / 2 + (vecMax_[i] - vecMin_[i]) * (getRandUniform01() - 0.5) * 0.89;
                }
            }
        }
    }

    //3.
    normVar(x, x0);
    var->opt->y = fun_->calcFun(x);
    if (updateVariable(var, forceUpdate))
    {
        return var->best->y < condition_.limitError;
    }

    return false;
}

}